#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>

/* default snap length (maximum bytes per packet to capture) */
#define SNAP_LEN 1518

/* ethernet headers are always exactly 14 bytes [1] */
#define SIZE_ETHERNET 14

/* Ethernet addresses are 6 bytes */
#define ETHER_ADDR_LEN	6

pcap_t* handle;								/* ETHERNET */

/* Ethernet header */
struct sniff_ethernet {
	u_char	ether_dhost[ETHER_ADDR_LEN];	/* destination host address */
	u_char	ether_shost[ETHER_ADDR_LEN];	/* source host address */
	u_short	ether_type;						/* IP? ARP? RARP? etc */
};

/* IP header */
struct sniff_ip {
	u_char	ip_vhl;					/* version << 4 | header length >> 2 */
	u_char	ip_tos;					/* type of service */
	u_short	ip_len;					/* total length */
	u_short	ip_id;					/* identification */
	u_short	ip_off;					/* fragment offset field */
	#define	IP_RF 0x8000			/* reserved fragment flag */
	#define	IP_DF 0x4000			/* dont fragment flag */
	#define	IP_MF 0x2000			/* more fragments flag */
	#define	IP_OFFMASK 0x1fff		/* mask for fragmenting bits */
	u_char	ip_ttl;					/* time to live */
	u_char	ip_p;					/* protocol */
	u_short	ip_sum;					/* checksum */
	struct	in_addr ip_src,ip_dst;	/* source and dest address */
};
#define IP_HL(ip)               (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip)                (((ip)->ip_vhl) >> 4)

/* TCP header */
typedef u_int tcp_seq;

struct sniff_tcp {
	u_short th_sport;				/* source port */
	u_short th_dport;				/* destination port */
	tcp_seq th_seq;					/* sequence number */
	tcp_seq th_ack;					/* acknowledgement number */
	u_char  th_offx2;				/* data offset, rsvd */
	#define TH_OFF(th)		(((th)->th_offx2 & 0xf0) >> 4)
	u_char  th_flags;
	#define TH_FIN  0x01
	#define TH_SYN  0x02
	#define TH_RST  0x04
	#define TH_PUSH 0x08
	#define TH_ACK  0x10
	#define TH_URG  0x20
	#define TH_ECE  0x40
	#define TH_CWR  0x80
	#define TH_FLAGS		(TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
	u_short th_win;					/* window */
	u_short th_sum;					/* checksum */
	u_short th_urp;					/* urgent pointer */
};

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
void print_payload(const u_char *payload, int len);
void print_hex_ascii_line(const u_char *payload, int len, int offset);
void ifprint( pcap_if_t *d );
char* iptos( u_long in );
char* ip6tos( struct sockaddr *sockaddr, char *address, int addrlen );

u_short get_src_port( const u_char *packet ){
	const struct sniff_ip *ip;				/* The IP header */
	const struct sniff_tcp *tcp;			/* The TCP header */
	int size_ip;
	int size_tcp;
	ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);

	/* define/compute ip header offset */
	size_ip = IP_HL(ip)*4;
	if (size_ip < 20) {
		printf("   * Invalid IP header length: %u bytes\n", size_ip);
		return -1;
	}
	/* define/compute tcp header offset */
	tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
	size_tcp = TH_OFF(tcp)*4;
	if (size_tcp < 20) {
		printf("   * Invalid TCP header length: %u bytes\n", size_tcp);
		return -1;
	}
	return ntohs(tcp->th_sport);
}

u_short get_dst_port( const u_char *packet ){
	const struct sniff_ip *ip;				/* The IP header */
	const struct sniff_tcp *tcp;			/* The TCP header */
	int size_ip;
	int size_tcp;
	ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);

	/* define/compute ip header offset */
	size_ip = IP_HL(ip)*4;
	if (size_ip < 20) {
		printf("   * Invalid IP header length: %u bytes\n", size_ip);
		return -1;
	}
	/* define/compute tcp header offset */
	tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
	size_tcp = TH_OFF(tcp)*4;
	if (size_tcp < 20) {
		printf("   * Invalid TCP header length: %u bytes\n", size_tcp);
		return -1;
	}
	return ntohs(tcp->th_dport);
}

/*
 * print data in rows of 16 bytes: offset   hex   ascii
 *
 * 00000   47 45 54 20 2f 20 48 54  54 50 2f 31 2e 31 0d 0a   GET / HTTP/1.1..
 */
void print_hex_ascii_line( const u_char *payload, int len, int offset ) {

	int i;
	int gap;
	const u_char *ch;

	/* offset */
	printf("%05d   ", offset);

	/* hex */
	ch = payload;
	for(i = 0; i < len; i++) {
		printf("%02x ", *ch);
		ch++;
		/* print extra space after 8th byte for visual aid */
		if (i == 7)
			printf(" ");
	}
	/* print space to handle line less than 8 bytes */
	if (len < 8)
		printf(" ");

	/* fill hex gap with spaces if not full line */
	if (len < 16) {
		gap = 16 - len;
		for (i = 0; i < gap; i++) {
			printf("   ");
		}
	}
	printf("   ");

	/* ascii (if printable) */
	ch = payload;
	for( i=0; i<len; i++ ) {
		if ( isprint(*ch) ){
			printf( "%c", *ch );
		} else {
			printf( "." );
		}
		ch++;
	}

	printf( "\n" );

return;
}

/* print packet payload data (avoid printing binary data) */
void print_payload(const u_char *payload, int len) {

	int len_rem = len;
	int line_width = 16;			/* number of bytes per line */
	int line_len;
	int offset = 0;					/* zero-based offset counter */
	const u_char *ch = payload;

	if (len <= 0){
		return;
	}

	/* data fits on one line */
	if (len <= line_width) {
		print_hex_ascii_line(ch, len, offset);
		return;
	}

	/* data spans multiple lines */
	for ( ;; ) {
		/* compute current line length */
		line_len = line_width % len_rem;
		/* print line */
		print_hex_ascii_line(ch, line_len, offset);
		/* compute total remaining */
		len_rem = len_rem - line_len;
		/* shift pointer to remaining bytes to print */
		ch = ch + line_len;
		/* add offset */
		offset = offset + line_width;
		/* check if we have line width chars or less */
		if (len_rem <= line_width) {
			/* print last line and get out */
			print_hex_ascii_line(ch, len_rem, offset);
			break;
		}
	}

return;
}

char* get_msg( int offset, const u_char *packet, char * message){
	strncpy(message, (char *) packet + offset, 4);
	message[5] = '\0';
	return message;
}

int find_cmd( int offset, const u_char *packet, const char* cmd ){
	char str1[MAX_PATH];
	char str2[MAX_PATH];

	strncpy(str1, (char *)( packet + offset ), 4);
	strncpy(str2, cmd, 4);

	str1[4] = '\0';
	str2[4] = '\0';

	return memcmp(str1, str2, 4);
}

pcap_dumper_t *pd; /* pointer to the dump file */
const char filename[] = "dump.txt";
char prestr[MAX_PATH];
int pcount = 0;

void dump( u_char *args, const struct pcap_pkthdr *header, const u_char *packet ){
	if ((pd = pcap_dump_open( handle, filename)) == NULL){
		fprintf( stderr, pcap_geterr(handle));
	}

	pcap_dump((u_char*) pd, header, packet);
}

/* dissect/print packet */
void got_packet( u_char *args, const struct pcap_pkthdr *header, const u_char *packet ) {

	/* declare pointers to packet headers */
	const struct sniff_ip *ip;				/* The IP header */
	const struct sniff_tcp *tcp;			/* The TCP header */
	// u_char *payload;						/* Packet payload */
	int size_ip;
	int size_tcp;
	char message[MAXCHAR];

	/* define/compute ip header offset */
	ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
	size_ip = IP_HL(ip)*4;
	if (size_ip < 20) {
		printf("   * Invalid IP header length: %u bytes\n", size_ip);
		return;
	}

	/* determine protocol */
	switch(ip->ip_p) {
		case IPPROTO_TCP:
			break;
		default:
			return;
	}

	/* OK, this packet is TCP. */
	/* define/compute tcp header offset */
	tcp = ( struct sniff_tcp* )( packet + SIZE_ETHERNET + size_ip );
	size_tcp = TH_OFF( tcp )*4;
	if ( size_tcp < 20 ) {
		printf("   * Invalid TCP header length: %u bytes\n", size_tcp);
		return;
	}

	/* Incoming from the server to us */
	if( ntohs(tcp->th_sport) == 21 ){
		printf("From server: %s\n", get_msg( SIZE_ETHERNET + size_ip + size_tcp, packet, message));
	}

	/* Outgoing from us to server*/
	if(ntohs(tcp->th_dport) == 21){
		printf("From us to server: %s\n", get_msg(SIZE_ETHERNET + size_ip + size_tcp, packet, message));
	}

	if (ntohs(tcp->th_sport)==21 || ntohs(tcp->th_dport)==21){
		
		if( !find_cmd( SIZE_ETHERNET + size_ip + size_tcp, packet, "RETR" )){
			dump( args, header, packet );
		}

		if( !find_cmd( SIZE_ETHERNET + size_ip + size_tcp, packet, "QUIT" ) ){
			printf("breaking loop.\n");
			pcap_breakloop( handle );
		}
	}
	return;
}

char* print_ipv4( pcap_if_t *d, char* ip4str ){
	pcap_addr_t* a;
	strncpy(ip4str, "", 1);

	for( a=d->addresses; a; a=a->next ){
		if( a->addr->sa_family == AF_INET ){
			strncpy(ip4str, iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr), INET_ADDRSTRLEN);
			break;
		}
	}
	return ip4str;
}

void ifprint( pcap_if_t *d ){
	pcap_addr_t *a;
	char ip6str[128];

	/* name */
	printf("%s\n", d->name);

	/* Description */
	if (d->description) {
		printf("\tDescription: %s\n", d->description);
	}

	/* Loopback Address */
	printf("\tLoopback: %s\n", (d->flags & PCAP_IF_LOOPBACK)? "yes":"no");

	/* IP addresses */
	for( a=d->addresses; a ; a=a->next ){
		printf("\tAddress Family: #%d\n", a->addr->sa_family);

		switch (a->addr->sa_family){
			case AF_INET:
				printf("\tAddress Family Name: AF_INET\n");
				if(a->addr) { printf( "\tAddress %s\n", iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr)); }
				if(a->netmask) { printf( "\tNetmask: %s\n", iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr)); }
				if(a->broadaddr) { printf( "\tBroadcast Address: %s\n", iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr)); }
				if(a->dstaddr) { printf( "\tDestination Address: %s\n", iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr)); }
				break;
			case AF_INET6:
				printf( "\tAddress Family Name: AF_INET6\n" );
				if(a->addr) { printf( "\tAddress: %s\n", ip6tos(a->addr, ip6str, sizeof(ip6str)) ); }
				break;
		default:
				printf( "\tAddress Family Name: Unknown\n" );
			break;
		} /* end switch */
	} /* end for */
	printf( "\n" );
} /* end function ifprint */

#define IPTOSBUFFERS 12
char* iptos( u_long in ){
	static char output[IPTOSBUFFERS][3*4+3+1];
	static short which;
	u_char* p;

	p = (u_char *)&in;
	which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
	_snprintf_s(output[which], sizeof(output[which]), sizeof(output[which]), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
	return output[which];
}

char* ip6tos( struct sockaddr* sockaddr, char* address, int addrlen ){
	socklen_t sockaddrlen;

	sockaddrlen = sizeof(struct sockaddr_in6);

	if( getnameinfo(
		  sockaddr,
		  sockaddrlen,
		  address,
		  addrlen,
		  NULL,
		  0,
		  NI_NUMERICHOST) != 0 ){ address = NULL; }
	return address;
}

/* Test for active adapter. Returns nonzero value for the adapter with an
    active IPV4 address. */
int test_adapter(pcap_if_t *d){
	pcap_addr_t* a = d->addresses;

	char string1[INET_ADDRSTRLEN+1];
	char string2[INET_ADDRSTRLEN+1];

	strcpy(string1, "0.0.0.0");

	for( a=d->addresses; a; a=a->next ){
		switch (a->addr->sa_family){
			case AF_INET:
				strcpy(string2, iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
				break;
			default:
				strcpy(string2, "0.0.0.0");
				break;
		}
	}

	return strncmp( string1, string2, INET_ADDRSTRLEN);
}

int main(int argc, char **argv) {

	char *dev = NULL;					/* capture device name */
	char errbuf[PCAP_ERRBUF_SIZE+1];	/* error buffer */
	char ip4str[INET_ADDRSTRLEN];
	char filter_exp[] = "ip";			/* filter expression [3] */
	struct bpf_program fp;				/* compiled filter program (expression) */
	bpf_u_int32 mask;					/* subnet mask */
	bpf_u_int32 net;					/* ip */
	int num_packets = 1;				/* number of packets to capture */
	pcap_if_t *alldevs;
	pcap_if_t *d;
	int i=0, j=0;


	/* Retrieve the device list */
	if(pcap_findalldevs(&alldevs, errbuf) == -1){
		fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
		exit(1);
	}

	/* Find the active adapter */
	for( i=0, d=alldevs; d; i++, d=d->next ){
		if( test_adapter(d) ){
			i++;
			break;
		}
	}

	if( i==0 ) {
		printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
		return -1;
	}

	/* Jump to the selected adapter */
	for( d=alldevs, j=0  ;  j<i-1  ;  d=d->next, j++ );
	dev = d->name;
	// ifprint(d);
	printf("Listening for FTP traffic to/from %s\n", print_ipv4(d, ip4str));

	/* get network number and mask associated with capture device */
	if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
		fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf);
		net = 0;
		mask = 0;
	}

	/* print capture info */
	printf("Number of packets: %d\n", num_packets);
	printf("Filter expression: %s\n", filter_exp);

	/* open capture device */
	handle = pcap_open_live(dev, SNAP_LEN, 1, 1000, errbuf);
	if (handle == NULL) {
		fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
		exit(EXIT_FAILURE);
	}

	/* make sure we're capturing on an Ethernet device [2] */
	if ( pcap_datalink(handle) != DLT_EN10MB ) {
		fprintf(stderr, "%s is not an Ethernet\n", dev);
		exit(EXIT_FAILURE);
	}

	/* compile the filter expression */
	if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
		fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
		exit(EXIT_FAILURE);
	}

	/* apply the compiled filter */
	if (pcap_setfilter(handle, &fp) == -1) {
		fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
		exit(EXIT_FAILURE);
	}

	/* now we can set our callback function */
	pcap_loop(handle, -1, got_packet, NULL);

	/* cleanup */
	pcap_freealldevs(alldevs);
	pcap_freecode(&fp);
	pcap_close(handle);

	printf("\nCapture complete.\n");

return 0;
}